Skip to main content

Spring AOP中的Advice

本文相关代码(来自官方源码spring-test模块)请参见spring-framework org.springframework.mylearntest包下。

  1. Before Advice

  2. After Advice

After returning

After throwing

After Advice(finally)

  1. After Around

Introduction

在AspectJ中称Inter-Type Declaration,在JBoss AOP中称Mix-in,都是指这同一种类型的Advice。与之前的几种Advice类型不同,Introduction不是根据横切逻辑在Joinpoint处的执行时机来区分的,而是根据它可以完成的功能而区别于其他Advice类型。

AspectJ采用静态织入的形式,那么对象在使用的时候,Introduction逻辑已经是编译织入完成的。所以理论上来说,AspectJ提供的Introduction类型的Advice,在现有Java平台上的AOP实现中是性能最好的;而像JBosss AOP或者Spring AOP等采用动态织入的AOP实现,Introduction的性能要稍逊一筹。

在Spring中,Advice按照其自身实例能否在目标对象类的所有实例中共享这一标准,可以划分为两大类,即per-class类型的Advice和per-instance类型的Advice

per-class

per-class的Advice是指,该类型的Advice的实例可以在目标对象类的所有实例之间共享。这种类型的Advice通常只是提供方法的拦截功能,不会对目标对象类保存任何状态或者添加新的特性。

BeforeAdvice

ResourceSetupBeforeAdvice
package org.springframework.mylearntest.aop.advice;

import org.apache.commons.io.FileUtils;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.core.io.Resource;

import java.lang.reflect.Method;

public class ResourceSetupBeforeAdvice implements MethodBeforeAdvice {
private Resource resource;

public ResourceSetupBeforeAdvice(Resource resource) {
this.resource = resource;
}

@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
if (!resource.exists()) {
FileUtils.forceMkdir(resource.getFile());
}
}
}

ThrowsAdvice

ExceptionBarrierThrowsAdvice
package org.springframework.mylearntest.aop.advice;

import org.omg.CORBA.portable.ApplicationException;
import org.springframework.aop.ThrowsAdvice;

import java.lang.reflect.Method;

public class ExceptionBarrierThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(Throwable t) {
// 普通异常处理
}

public void afterThrowing(RuntimeException t) {
// 运行时异常处理
}

public void afterThrowing(Method m, Object[] args, Object target, ApplicationException e) {
// 处理应用程序生成的异常
}
}

AfterReturningAdvice

此Advice可以访问到当前Joinpoint的方法返回值、方法、方法参数以及所在的目标对象,但是不能更改返回值,可以使用Around Advice来更改返回值。

Around Advice

Spring中没有定义Around Advice ,而是直接使用AOP Alliance的标准接口,实现 MethodInterceptor即可。

per-instance

per-instance类型的Advice不会在目标类所有对象实例之间共享,而是会为不同的实例对象保存它们各自的状态以及相关逻辑。在Spring中Introduction就是唯一的一种per-instance型Advice。

Introduction 可以在不改动目标类定义的情况下,为目标类添加新的属性以及行为。

在Spring中,为目标对象添加新的属性和行为必须声明相应的接口以及相应的实现。这样,再通过特定的拦截器将新的接口定义以及实现类中的逻辑附加到目标对象之上。之后,目标对象就拥有了新的状态和行为。这个特定的拦截器是org.springframework.aop.IntroductionInterceptor。

Introduction继承了MethodInterceptor以及DynamicIntroductionAdvice,通过DynamicIntroductionAdvice,我们可以界定当前的IntroductionInterceptor为哪些接口类提供相应的拦截功能。通过MethodInterceptor,IntroductionInterceptor就可以处理新添加的接口上的方法调用了。通常情况下,对于IntroductionInterceptor来说,如果是新增加的接口上的方法调用,不必去调用MethodInterceptor的proceed()方法。当前被拦截的方法实际上是整个调用链中要最终执行的唯一方法。

Introduction相关类图

  • Introduction型的Advice两条分支,即以DynamicIntroductionAdvice为首的动态分支(不共享)和以IntroductionInfo为首的静态(共享)。

  • DynamicIntroductionAdvice不用预先设定目标接口类型;而IntroductionInfo则完全相反,实现类必须返回预定目标接口的类型。

IntroductionInfo
public interface IntroductionInfo {
Class[] getInterfaces();
}

如果需要对目标对象拦截并添加Introduction逻辑,可以使用现有两个实现类即可DelegatingIntroductionInterceptor,DelegatePerTargetObjectIntroductionInterceptor。

DelegatingIntroductionInterceptor

DelegatingIntroductionInterceptor不会自己实现将要添加到目标对象上的新逻辑行为,而是委派给其他的实现类。

使用DelegatingIntroductionInterceptor增强Developer。接口省略。

Developer
package org.springframework.mylearntest.aop.advice.perinstance.delegatingIntroductionInterceptor;

public class Developer implements IDeveloper{
@Override
public void developSoftware() {
System.out.println(" do some developing ...");
}
}

为新的状态和行为定义接口。我们要为实现类添加增强的功能,首先需要将需求的职能以接口定义的形式声明。

ITtester
package org.springframework.mylearntest.aop.advice.perinstance.delegatingIntroductionInterceptor;

public interface ITester {
boolean isBusyAsTester();
void testSoftware();
}

给出新的接口的实现类。接口实现类给出将要添加到目标对象的具体逻辑。当目标对象要行使新的职能的时候,会通过该实现类寻求帮忙。

Tester
package org.springframework.mylearntest.aop.advice.perinstance.delegatingIntroductionInterceptor;

public class Tester implements ITester{
private boolean busyAsTester;

public void setBusyAsTester(boolean busyAsTester) {
this.busyAsTester = busyAsTester;
}

@Override
public boolean isBusyAsTester() {
return busyAsTester;
}

@Override
public void testSoftware() {
System.out.println("do some developing and test ...");
}
}

通过DelegatingIntroductionInterceptor进行Introduction拦截。有了新增加的职能的接口以及相应的实现类,使用DelegatingIntroductionInterceptor,我们可以把具体的Introduction拦截委托给具体的实现类来完成。

如何织入
ITester delegator = new Tester();
DelegatingIntroductionInterceptor interceptor = new DelegatingIntroductionInterceptor(delegator);

// 进行织入
ITester tester = (ITester)weaver.weave(developer).with(interceptor).getProxy();
tester.testSoftware();

虽然,DelegatingIntroductionInterceptor是Introduction型Advice的一个实现,但它的实现根本就有兑现Introduction作为per-instance 型的承诺。实际上DelegatingIntroductionInterceptor会使用它所持有的同一个"delegate" 接口实例,供同一目标类的所有实例共享使用。如果要想严格达到Introduction型Advice的效果,我们应该使用DelegatePerTargetObjectIntroductionInterceptor。

DelegatePerTargetObjectIntroductionInterceptor

与DelegatingIntroductionInterceptor不同,DelegatePerTargetObjectIntroductionInterceptor会在内部持有一个目标对象与相应Introduction逻辑实现类之间的映射关系。当每个对象上的新定义的接口方法被调用的时候,DelegatePerTargetObjectIntroductionInterceptor会拦截这些调用,然后以目标对象实例作为键,到它持有的那个映射关系中取得对应当前目标对象实例的Introduction实现实例。

DelegatePerTargetObjectIntroductionInterceptor interceptor1 =
new DelegatePerTargetObjectIntroductionInterceptor(Tester.class,ITester.class);

扩展DelegatingIntroductionInterceptor

TesterFeatureIntroductionInterceptor
package org.springframework.mylearntest.aop.advice.perinstance;

import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.lang3.StringUtils;
import org.springframework.aop.support.DelegatingIntroductionInterceptor;

public class TesterFeatureIntroductionInterceptor extends DelegatingIntroductionInterceptor implements ITester {

public static final long serialVersionUID = -3387097489523045796L;
private boolean busyAsTester;

@Override
public Object invoke(MethodInvocation mi) throws Throwable {
if (isBusyAsTester() && StringUtils.contains(mi.getMethod().getName(), "developSoftware")) {
throw new RuntimeException("I'am so tired");
}
return super.invoke(mi);
}

@Override
public boolean isBusyAsTester() {
return busyAsTester;
}

public void setBusyAsTester(boolean busyAsTester) {
this.busyAsTester = busyAsTester;
}

@Override
public void testSoftware() {
System.out.println("I will ensure the quality");
}
}